package com.kongjs.common.rest.advice;

import jakarta.annotation.Resource;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

import java.util.*;


@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class GlobalErrorController implements ErrorController {
    private final ErrorAttributes errorAttributes;
    private final List<ErrorViewResolver> errorViewResolvers;

    @Resource
    private GlobalControllerAdvice globalControllerAdvice;
    @Resource
    private GlobalRestControllerAdvice globalRestControllerAdvice;

    public GlobalErrorController(ErrorAttributes errorAttributes, List<ErrorViewResolver> errorViewResolvers) {
        this.errorAttributes = errorAttributes;
        this.errorViewResolvers = sortErrorViewResolvers(errorViewResolvers);
    }

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(NativeWebRequest webRequest) {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        HttpStatus status = getStatus(Objects.requireNonNull(request));
        ModelAndView errorView = resolveErrorView(request, response, status, new HashMap<>());
        ModelAndView exceptionView;
        Throwable ex = errorAttributes.getError(webRequest);
        if (ObjectUtils.isEmpty(ex)) {
            exceptionView = globalControllerAdvice.handleGlobalException((Exception) ex, webRequest);
        } else {
            try {
                exceptionView = globalControllerAdvice.handleException((Exception) ex, webRequest);
            } catch (Exception e) {
                exceptionView = globalControllerAdvice.handleGlobalException(e, webRequest);
            }
        }
        if (errorView == null) {
            return exceptionView;
        }
        errorView.addAllObjects(exceptionView.getModelMap());
        return errorView;
    }

    @RequestMapping
    public ResponseEntity<Object> error(WebRequest request) {
        Throwable ex = errorAttributes.getError(request);
        if (ObjectUtils.isEmpty(ex)) {
            return globalRestControllerAdvice.handleGlobalException((Exception) ex, request);
        }
        try {
            return globalRestControllerAdvice.handleException((Exception) ex, request);
        } catch (Exception e) {
            return globalRestControllerAdvice.handleGlobalException(e, request);
        }
    }

    protected HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        try {
            return HttpStatus.valueOf(statusCode);
        } catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

    private List<ErrorViewResolver> sortErrorViewResolvers(List<ErrorViewResolver> resolvers) {
        List<ErrorViewResolver> sorted = new ArrayList<>();
        if (resolvers != null) {
            sorted.addAll(resolvers);
            AnnotationAwareOrderComparator.sortIfNecessary(sorted);
        }
        return sorted;
    }

    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        if (this.errorViewResolvers != null) {
            for (ErrorViewResolver resolver : this.errorViewResolvers) {
                ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
                if (modelAndView != null) {
                    return modelAndView;
                }
            }
        }
        return null;
    }
}
